
/**
 * 
 * pubsub.js
=========
https://github.com/Sahadar/pubsub.js

## Examples

//subscribe to 'hello/world' namespace
pubsub.subscribe('hello/world', function() {
        console.log('hello world!');
});
//publish event on 'hello/world' namespace
pubsub.publish('hello/world');
//prints "hello world" inside console

### Publish with parameter
//subscribe to 'hello/world' namespace
pubsub.subscribe('hello/world', function(data) {
        console.log(data);
});
//publish event on 'hello/world' namespace
pubsub.publish('hello/world', ['hello!']); // second parameter is an array of arguments
//prints "hello!" inside console

### Unsubscribe
//subscribe to 'hello/world' namespace
var subscribtion = pubsub.subscribe('hello/world', function() {
        console.log('hello world!');
});
//publish event on 'hello/world' namespace
pubsub.publish('hello/world');
//prints "hello world" inside console

//unsubscribe
pubsub.unsubscribe(subscribtion);
//publish event on 'hello/world' namespace
pubsub.publish('hello/world');
//nothing happen - we've previously unsubscribed that subscribtion

### Changing default configuration

***Browser***

**Before pubsub script loader** - make global variable named "pubsub" with your default configuration
pubsub = {
        separator : '.'
}

 ***After pubsub load - use it with your configuration, pubsub.js will replace that previous "pubsub" global variable with its own instance**
//subscribe to 'hello.world' namespace
var subscribtion = pubsub.subscribe('hello.world', function() {
        console.log('hello world!');
});
//publish event on 'hello.world' namespace
pubsub.publish('hello.world');
//prints "hello world" inside console

//unsubscribe
pubsub.unsubscribe(subscribtion);
//publish event on 'hello.world' namespace
pubsub.publish('hello.world');
//nothing happen - we've previously unsubscribed that subscribtion

### Event inheritance
//subscribe to 'hello' namespace
var subscribtion = pubsub.subscribe('hello', function() {
        console.log('hello world!');
});
//publish event on 'hello/world' namespace
pubsub.publish('hello/world', true);
//prints "hello world" inside console
//first event goes to "hello" namespace
//then it tries to execute on "hello/world" but nothing is listening on it

### Method: subscribeOnce
var iterator = 0;
var data = null;

pubsub.subscribeOnce('hello/world', function(param) {
        data = param;
        iterator++;
});
pubsub.publish('hello/world', ['hello']);
pubsub.publish('hello/world', ['world']);
console.log(iterator); //1
console.log(data); //'hello'

### Wildcard "*" <-- one namespace deeper
var number = 0;

//subscribe to "hello/world" namespace
pubsub.subscribe('hello/world', function() {
        number++;
});
//subscribe to "hello/earth" namespace
pubsub.subscribe('hello/earth', function() {
        number++;
});
//subscribe to "hello/galaxy" namespace
pubsub.subscribe('hello/galaxy', function() {
        number++;
});
//subscribe to "hello/world/inner" namespace
pubsub.subscribe('hello/world/inner', function() {
        number++;
});

pubsub.publish('hello/*');
//hello/* executes:
//	hello/world, hello/earth, hello/galaxy
//	namespace, hello/world/inner is not executed
//	
//	"*" goes only one namespace deeper
console.log(number); //3

### Multiple subscribtion
**many namespaces, one callback**
var number = 0;

var subscribtion = pubsub.subscribe(['hello/world', 'goodbye/world'], function() {
        number++;
});

pubsub.publish('hello/world');
console.log(number); //1
pubsub.publish('goodbye/world');
console.log(number); //2
pubsub.unsubscribe(subscribtion);

pubsub.publish('hello/world');
console.log(number); //2
pubsub.publish('goodbye/world');
console.log(number); //2

**one namespace, many callbacks**
var number1 = 0;
var number2 = 0;

var subscribtion = pubsub.subscribe('hello/world', [function() {
        number1++;
}, function() {
        number2 += 2;
}]);

pubsub.publish('hello/world');
console.log(number1 + ',' + number2); //1,2
pubsub.unsubscribe(subscribtion);

pubsub.publish('hello/world');
console.log(number1 + ',' + number2); //2,4

**many namespaces, many callbacks**
var number1 = 0;
var number2 = 0;

var subscribtion = pubsub.subscribe(['hello/world', 'goodbye/world'], [function() {
        number1++;
}, function() {
        number2 += 2;
}]);

pubsub.publish('hello/world');
console.log(number1 + ',' + number2); //1,2
pubsub.publish('goodbye/world');
console.log(number1 + ',' + number2); //2,4
pubsub.unsubscribe(subscribtion);

pubsub.publish('hello/world');
console.log(number1 + ',' + number2); //2,4
pubsub.publish('goodbye/world');
console.log(number1 + ',' + number2); //2,4

###making new instances with own namespaces scope
var number1 = 0;
var number2 = 0;

var privatePubsub = pubsub.newInstance();

pubsub.subscribe('hello/world', function() {
        number1++;
});

privatePubsub.subscribe('hello/world', function() {
        number2++;
});

pubsub.publish('hello/world');
console.log(number1 + ',' + number2); //1,0

privatePubsub.publish('hello/world');
console.log(number1 + ',' + number2); //1,1

Default pubsub.js configuration:
separator : '/' //defined namespace separator
recurrent : false // defines inheritance of publish event
log       : false // set to true will log unsubscribed namespaces to which You publish event

## License MIT
 */
(function(scope) {
	var pubsubInstance = null;
	var pubsubConfig = null;

	if(typeof pubsub === 'object') {
		pubsubConfig = pubsub;
	//node.js config from global
	} else if(typeof global === 'object' && typeof global.pubsubConfig === 'object') {
		pubsubConfig = global.pubsubConfig;
	}

	'use strict';
	function Pubsub(config) {
		var _eventObject = {};
		var options = {
			separator : (config && config.separator) ?  config.separator : '/',
			recurrent : (config && config.recurrent) ?  config.recurrent :  (false),
			log       : (config && config.log) ?  config.log :  (false)
		};
		
		function forEach(dataArray, callback) {
			var i = 0,
				arrayLength = dataArray.length;

			for(i = 0; i < arrayLength; i++) {
				callback(i);
			}
		}

		function executeCallback(subscribtions, args) {
			//clone array - callbacks can unsubscribe other subscribtions
			var executedSubscribtions = subscribtions.slice();

			forEach(executedSubscribtions, function(subscribtionId) {
				var subscribtion = null;

				if(typeof executedSubscribtions[subscribtionId] === 'object' && executedSubscribtions.hasOwnProperty(subscribtionId)) {
					subscribtion = executedSubscribtions[subscribtionId];
					subscribtion.callback.apply(subscribtion.object, args);
				}
			});
		}

		function executeWildcard(nsObject, args) {
			var nsElement;
			for(nsElement in nsObject) {
				if(nsElement !== '_events' && nsObject.hasOwnProperty(nsElement)) {
					executeCallback(nsObject[nsElement]['_events'], args);
				}
			}
		}

		function subscribe(ns_string, callback, givenObject) {
			var that = this,
				parts = ns_string.split(options.separator),
				nsObject, //Namespace object to which we attach event
				givenObjectSet = (givenObject) ? true : false,
				givenObject = (givenObjectSet) ? givenObject : callback,
				eventObject = null,
				i = 0;

			//Iterating through _eventObject to find proper nsObject
			nsObject = _eventObject;
			for (i = 0; i < parts.length; i += 1) {
				if (typeof nsObject[parts[i]] === "undefined") {
					nsObject[parts[i]] = {};
					nsObject[parts[i]]['_events'] = [];
				}
				nsObject = nsObject[parts[i]];
			}
			
			eventObject = {
				callback	: callback,
				object		: givenObject // "this" parameter in executed function
			};

			nsObject['_events'].push(eventObject);
			return {namespace : parts.join(options.separator),
				event : eventObject };
		}

		function unsubscribe (subscribeObject) {
			var that = this,
				ns_string = subscribeObject['namespace'],
				eventObject = subscribeObject['event'],
				parts = ns_string.split(options.separator),
				nsObject, 
				i = 0;

			//Iterating through _eventObject to find proper nsObject
			nsObject = _eventObject;
			for (i = 0; i < parts.length; i += 1) {
				if (typeof nsObject[parts[i]] === "undefined") {
					if(options.log) {
						console.error('There is no ' + ns_string + ' subscription');
					}
					return null;
				}
				nsObject = nsObject[parts[i]];
			}
			
			forEach(nsObject['_events'], function(eventId){
		        if(nsObject['_events'][eventId] === eventObject) {
		        	nsObject['_events'].splice(eventId, 1);
		        }
		    });
		}

		return {
			/**
			 * Publish event
			 * @param ns_string string namespace string splited by dots
			 * @param args array of arguments given to callbacks
			 * @param recurrent bool should execution be bubbled throught namespace
			 * @param depth integer how many namespaces separated by dots will be executed
			 */
			publish : function(ns_string, args, recurrent, depth) {
				var that = this,
					parts = ns_string.split(options.separator),
					recurrent = recurrent || options.recurrent, // bubbles event throught namespace if true
					nsObject, //Namespace object to which we attach event
					args = (args) ? args : [],
					partsLength = parts.length,
					iPart = null,
					i;

				nsObject = _eventObject;
				for (i = 0; i < partsLength; i++) {
					iPart = parts[i];
					if(iPart === '*') {
						executeWildcard(nsObject, args);
						return null;
					} else if (typeof nsObject[iPart] === "undefined") {
						if(options.log) {
							console.warn('There is no ' + ns_string + ' subscription');
						}
						return null;
					}
					nsObject = nsObject[iPart];
					
					if(recurrent === true && typeof depth !== 'number') { //depth is not defined
						executeCallback(nsObject['_events'], args);
					} else if(recurrent === true && typeof depth === 'number' && i >= partsLength - depth) { //if depth is defined
						executeCallback(nsObject['_events'], args);
					}
				}
				
				if(recurrent === false) {
					executeCallback(nsObject['_events'], args);
				}
			},
			/**
			 * Subscribe event
			 * @param ns_string string namespace string splited by dots
			 * @param callback function function executed after publishing event
			 * @param givenObject object/nothing Optional object which will be used as "this" in callback
			 */
			subscribe : function(ns_string, callback, givenObject) {
				var that = this,
					subscribtions = [];

				//if we have array of callbacks - multiple subscribtion
				if(typeof callback === 'object' && callback instanceof Array) {
					forEach(callback, function(number) {
						var oneCallback = callback[number];

						subscribtions =	subscribtions.concat(that.subscribe.apply(that, [ns_string, oneCallback, givenObject]));
					});
				} else if(typeof ns_string === 'object' && ns_string instanceof Array) {
					forEach(ns_string, function(number) {
						var namespace = ns_string[number];

						subscribtions =	subscribtions.concat(that.subscribe.apply(that, [namespace, callback, givenObject]));
					});
				} else {
					return subscribe.apply(that, arguments);
				}
				return subscribtions;
			},
			subscribeOnce : function(ns_string, callback, givenObject) {
				var that = this;
				var subscribtion = null;
				var subscribtionCallback = function() {
						callback.apply(this, arguments);
						that.unsubscribe(subscribtion);
					};

				subscribtion = that.subscribe.apply(that, [ns_string, subscribtionCallback, givenObject]);
			},
			unsubscribe : function(subscribeObject) {
				var that = this;

				//if we have array of callbacks - multiple subscribtion
				if(subscribeObject instanceof Array) {
					forEach(subscribeObject, function(number) {
						var oneSubscribtion = subscribeObject[number];

						unsubscribe.apply(that, [oneSubscribtion]);
					});
				} else {
					unsubscribe.apply(that, arguments);
				}
			},
			newInstance : function(config) {
				return new Pubsub(config);
			}
		}; //return block
	}
	pubsubInstance = new Pubsub(pubsubConfig);

	//if sbd's using requirejs library to load pubsub.js
	if(typeof define === 'function') {
		define(pubsubInstance);
	}

	//node.js
	if(typeof module === 'object' && module.exports) {
		module.exports = pubsubInstance;
	}

	if(window) {
		window.pubsub = pubsubInstance;
	}
	if(window && window !== scope) {
		scope.pubsub = pubsubInstance;
	}
})(this);